/*
 * Developer : Jack Matthews
 * Email: developer@droidprofessor.com
 * 
 * Copyright (C) 2010 http://droidprofessor.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.droidprofessor.android.licensegenerator.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;

import android.content.Context;
import android.content.Intent;
import android.util.Log;

import com.droidprofessor.android.library.phonelicenses.BackgroundHelperService;
import com.droidprofessor.android.library.phonelicenses.PreferencesHelper;
import com.droidprofessor.android.library.phonelicenses.R;
import com.droidprofessor.licensegenerator.lib.CoreClientLicense;
import com.droidprofessor.licensegenerator.lib.EnablingFeatureFailedException;
import com.droidprofessor.licensegenerator.lib.Feature;
import com.droidprofessor.licensegenerator.lib.LicenseLoader;

/**
 * A helper singleton wrapper for the LicenseLoader class.
 * Helps to make loading licenses simpler for the client 
 * application and automatically includes the XML strings.
 * 
 * @author Jack Matthews
 *
 */
public enum AndroidClientLicenseImpl implements AndroidClientLicense {
	INSTANCE;
	private static final String TAG = "License";
	
//	private static final AndroidClientLicenseImpl license = new AndroidClientLicenseImpl();
	private CoreClientLicense clientLicense; //does the real work
	
	private AndroidClientLicenseImpl() {};
	
	//These fields should be overridden in string.xml
	private String licensingSystem = null;
	private String authKey = null;
	private String salt = null;
	private String applicationId = null;
	private String signatureFileName = null;
	private String host = null;
	private String scheme = null;
	private int port = 80;
	private String path = null;
	private String base64PublicKey = null;
	private LicenseType applicationType;
	
	
	public static AndroidClientLicense getInstance() {
		return AndroidClientLicenseImpl.INSTANCE;
	}
	
	
	@Override
	public void init(Context cnx, LicenseType restriction, String licensingSystem, String authKey, String salt,
			String base64PublicKey, String signatureFileName, String applicationId,
			String scheme, String host, String port, String path) {
		//Validation
		if(cnx==null) throw new IllegalArgumentException("cnx cannot be null");
		if(licensingSystem==null) throw new IllegalArgumentException("licensingSystem cannot be null");
		if(authKey==null) throw new IllegalArgumentException("authKey cannot be null");
		if(salt==null) throw new IllegalArgumentException("salt cannot be null");
		if(base64PublicKey==null) throw new IllegalArgumentException("base64PublicKey cannot be null");
		if(signatureFileName==null) throw new IllegalArgumentException("signatureFileName cannot be null");
		if(applicationId==null) throw new IllegalArgumentException("applicationId cannot be null");
		if(scheme==null) throw new IllegalArgumentException("scheme cannot be null");
		if(host==null) throw new IllegalArgumentException("host cannot be null");
		int intPort = Integer.valueOf(port);
		if(intPort<1) throw new IllegalArgumentException("port cannot be of value "+intPort);
		if(path==null) throw new IllegalArgumentException("path cannot be null");
		
		//save for later use
		this.salt = salt;
		this.signatureFileName = signatureFileName;
		this.applicationId = applicationId;
		this.scheme = scheme;
		this.host = host;
		this.port = intPort;
		this.path = path;
		this.base64PublicKey = base64PublicKey;
		this.licensingSystem  = licensingSystem;
		this.authKey = authKey;
		this.applicationType = restriction;

		
		setupCoreFeatures(cnx);
		
		//check if a license needs to be downloaded
		if(!isLicenseInstalled(cnx)) {
			checkPreferencesAndDownloadLicense(cnx); 
		}
		
		
		//Init the LicenseLoader to do the real work
		try {
			byte[] licenseData = loadLicenseContents(cnx);
			this.clientLicense = LicenseLoader.getInstance();
			this.clientLicense.init(  
					base64PublicKey.getBytes( "ASCII" ), 
					licenseData
					);
			
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new IllegalStateException("ASCII is not a supported format");
		}
		
	
	}

	
	

	/**
	 * These are the core features that must be included in 
	 * all license files.
	 */
	private void setupCoreFeatures(Context cnx) {
		try {
			Feature.DEVICE_ID.enable(cnx, salt);			
			Feature.APPLICATION_ID.enable(cnx, salt, this.applicationId);
			Feature.USERNAME.enable(cnx, salt, PreferencesHelper.getUsername(cnx));
			Feature.TIMESTAMP.enable(cnx, salt, new Date().toString());
			Feature.LICENSING_SYSTEM.enable(cnx, salt, this.licensingSystem);
		} catch (EnablingFeatureFailedException e) {
			e.printStackTrace();
			throw new IllegalStateException("Enabling Core Features failed");
		}
	}


	/**
	 * Load the license contents
	 * 
	 * @return the contents of the license or null on IOException
	 * 
	 */
	private byte[] loadLicenseContents(Context cnx) {
		int length = (int)getLicenseFile(cnx).length();
		byte[] contents = new byte[length];
		
		try {
			FileInputStream reader = cnx.openFileInput(this.signatureFileName);
			reader.read(contents);
			reader.close();
		} catch (IOException e) {
			Log.i(TAG, "No License Installed");
		}
		
		return contents;
	}


	
	/**
	 * Attempts to download the license automatically
	 * as long as the user has verified their account
	 * details and there is no existing license file
	 * installed.
	 * 
	 * @see AndroidClientLicenseImpl#downloadAndInstallLicense()
	 * 
	 */
	private void checkPreferencesAndDownloadLicense(Context cnx) {
		if(PreferencesHelper.isAccountVerified(cnx) && !isLicenseInstalled(cnx) ) {
			downloadAndInstallLicense(cnx);
		}
	}

	
	

	@Override
	public File getLicenseFile(Context cnx) {
		return new File(cnx.getFilesDir()+File.separator+this.signatureFileName);
	}


	@Override
	public void downloadAndInstallLicense(Context cnx) {
		
		//Setup required Features
		setupCoreFeatures(cnx); //Refresh to get current app id and timestamp
		
//		//Required Feature Set
//		if(!Feature.isEnabled(Feature.APPLICATION_ID))
//			throw new IllegalStateException("Feature.APPLICATION_ID must be enabled");
//		if(!Feature.isEnabled(Feature.USERNAME))
//			throw new IllegalStateException("Feature.USERNAME must be enabled");
//		if(!Feature.isEnabled(Feature.TIMESTAMP))
//			throw new IllegalStateException("Feature.TIMESTAMP must be enabled");
//		if(!Feature.isEnabled(Feature.LICENSING_SYSTEM))
//			throw new IllegalStateException("Feature.LICENSING_SYSTEM must be enabled");

		//reload to refresh username and timestamp to current values
//		reload(); 
		
		Intent service = new Intent(cnx, BackgroundHelperService.class);
		service.setAction(BackgroundHelperService.ACTION_DOWNLOAD_LICENSE);
		cnx.startService(service);
	}
	
	
	
	
	@Override
	public void deleteAccountDetails(Context cnx) {
		PreferencesHelper.updatePreferences(cnx, "","", "", "");
		PreferencesHelper.setAccountUnverified(cnx);
	}

	
	
	@Override
	public void deleteLicense(Context cnx) {

		if( isLicenseInstalled(cnx) ) {
			if(getLicenseFile(cnx).delete()) {
				Log.d(TAG, "Licence File Deleted");
			}
			else {
				Log.e(TAG, "Unable to delete: "+getLicenseFile(cnx).toString());	
			}
		} else {
			Log.e(TAG, "File does not exist: "+getLicenseFile(cnx).toString());		
		}
	}
	
	
	
	@Override
	public String getLicenseDetails(Context cnx) {
		String pass = cnx.getString(R.string.com_phonelicenses_pass);
		String fail = cnx.getString(R.string.com_phonelicenses_fail);
		
		StringBuilder licenseInfo = new StringBuilder();

		//Here we check if the installed license is valid for our phone
		if(Feature.isEnabled(Feature.TIMESTAMP))
			licenseInfo.append( "\n"+"Created on: "+getCreationTimestamp() );
		if(Feature.isEnabled(Feature.LICENSING_SYSTEM))
			licenseInfo.append( "\n"+"Licensing System: "+getLicensingSystem() );
		if(Feature.isEnabled(Feature.USERNAME))
			licenseInfo.append( "\n"+"Username: "+getUsername() );
		if(Feature.isEnabled(Feature.DEVICE_ID)) {
			licenseInfo.append( "\n"+cnx.getString(R.string.com_phonelicenses_device_id_label) );
			licenseInfo.append( isDeviceIdEqual()?pass:fail );
		}
		if(Feature.isEnabled(Feature.PHONE_NUMBER)) {
			licenseInfo.append( "\n"+cnx.getString(R.string.com_phonelicenses_phone_number_label) );
			licenseInfo.append( isPhoneNumberEqual()?pass:fail );			
		}
		if(Feature.isEnabled(Feature.ANDROID_BOOT_ID)) {
			licenseInfo.append( "\n"+cnx.getString(R.string.com_phonelicenses_android_boot_id_label) );
			licenseInfo.append( isAndroidBootIdEqual()?pass:fail );			
		}
		if(Feature.isEnabled(Feature.APPLICATION_ID)) {
			licenseInfo.append( "\n"+cnx.getString(R.string.com_phonelicenses_application_id_label) );
			licenseInfo.append( isApplicationIdEqual(this.applicationId)?pass:fail );
		}
		if(Feature.isEnabled(Feature.EXPIRY)) {
			licenseInfo.append( "\n"+cnx.getString(R.string.com_phonelicenses_still_current_label) );
			licenseInfo.append( isLicenseStillCurrent()?pass:fail );
			
			if(isLicenseStillCurrent()) {
				SimpleDateFormat sdf = new SimpleDateFormat("EEE, MMM d, yyyy");
				Date d = new Date(getExpiry());
				licenseInfo.append( " (Expires: "+sdf.format(d)+")");
			} else {
				licenseInfo.append( " (Expired)");
			}
		}

		return licenseInfo.toString();
	}



	@Override
	public boolean isLicenseInstalled(Context cnx) {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return true;
		return getLicenseFile(cnx).exists();
	}


	@Override
	public boolean isAndroidBootIdEqual() {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return true;
		return clientLicense.isAndroidBootIdEqual();
	}



	@Override
	public boolean isApplicationIdEqual(String appname) {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return true;
		return clientLicense.isApplicationIdEqual(appname);
	}



	@Override
	public boolean isDeviceIdEqual() {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return true;
		return clientLicense.isDeviceIdEqual();
	}



	@Override
	public boolean isLicenseStillCurrent() {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return true;
		return clientLicense.isLicenseStillCurrent();
	}



	@Override
	public boolean isPhoneNumberEqual() {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return true;
		return clientLicense.isPhoneNumberEqual();
	}



	@Override
	public String getCreationTimestamp() {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return "DISABLED";
		return clientLicense.getCreationTimestamp();
	}



	@Override
	public String getLicensingSystem() {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return "DISABLED";
		return clientLicense.getLicensingSystem();
	}



	@Override
	public String getUsername() {
		if(this.applicationType == LicenseType.DISABLE_LICENSE) return "DISABLED";
		return clientLicense.getUsername();
	}

	

	@Override
	public void init(byte[] base64PublicKey, byte[] base64Signature) {
		throw new UnsupportedOperationException("This operation is not supported. Use the other init() method instead!");
	}


	@Override
	public String getApplicationId() {
		return this.applicationId;
	}


	@Override
	public String getHost() {
		return this.host;
	}


	@Override
	public String getPath() {
		return this.path;
	}


	@Override
	public int getPort() {
		return this.port;
	}


	@Override
	public String getSalt() {
		return this.salt;
	}


	@Override
	public void reload(Context cnx) {
		init(cnx, this.applicationType, this.licensingSystem, this.authKey, this.salt, this.base64PublicKey, this.signatureFileName, this.applicationId, this.scheme, this.host, String.valueOf(this.port), this.path);
	}


	@Override
	public long getExpiry() {
		if(applicationType == LicenseType.DISABLE_LICENSE) return 0;
		return clientLicense.getExpiry();
	}


	@Override
	public String getAuthKey() {
		return this.authKey;
	}


	@Override
	public String getScheme() {
		return this.scheme;
	}


	@Override
	public LicenseType getLicenseType() {
		return this.applicationType;
	}


	@Override
	public URI getWebsiteUri() {
		URI uri = null;
		//If the port is 80 don't show it
		int fPort = this.port==80?-1:this.port;
		//remove index.php from the end of the path
		String fPath = this.path.substring(0, this.path.length()-"index.php".length());
		try {
			uri = URIUtils.createURI(this.scheme, this.host, fPort, fPath, null, null);
		} catch (URISyntaxException e) {
			Log.e(TAG, "Unable to form a URI", e);
		}

		return uri;
	}
	
	
	@Override
	public URI getCrossPromotionUri() {
		URI uri = null;
		
		List<NameValuePair> query = new ArrayList<NameValuePair>();
		query.add(new BasicNameValuePair("option", "com_phonelicenses"));
		query.add(new BasicNameValuePair("view", "apps"));
		query.add(new BasicNameValuePair("format", "raw"));
		
		try {
			uri = URIUtils.createURI(this.scheme, this.host, this.port, this.path, URLEncodedUtils.format(query, HTTP.UTF_8), null);
		} catch (URISyntaxException e) {
			Log.e(TAG, "Unable to form URI", e);
		}

		Log.d(TAG, uri.toString());
		return uri;
	}

}
